<?php
/**
 * @Author: Marte
 * @Date:   2020-07-08 10:58:07
 * @Last Modified by:   Marte
 * @Last Modified time: 2020-07-08 17:14:52
 */

class Timer extends SplMinHeap
{
    /**
    * 实现SplHeap抽象方法
    * 比较根节点和新插入节点大小
    * @param mixed $value1
    * @param mixed $value2
    * @return int
    */
    protected function compare($value1, $value2)
    {
        if ($value1['timeout'] > $value2['timeout']) {
            return -1;
        }

        if ($value1['timeout'] < $value2['timeout']) {
            return 1;
        }

        return 0;
    }

    /**
    * 插入节点
    * @param int $expire   过期时间
    * @param bool $repeat  是否重复
    * @param mixed $action  回调函数
    */
    public function insert($expire, $repeat = false, \Closure $action = null)
    {
        $value['expire'] = $expire;
        $value['timeout'] = time() + $expire;
        $value['repeat'] = $repeat;
        $value['action'] = $action;

        parent::insert($value);
    }

    /**
    * 监听
    * @param bool $debug
    */
    public function monitor($debug = false)
    {
        // 判断是否为空堆
        go(function() use($debug){
            while (!$this->isEmpty()) {
                $this->exec($debug);
                co::sleep(0.001);
            }
        });
    }

    /**
    * 执行
    * @param $debug
    */
    private function exec($debug)
    {
        $hit = 0;
        $t1  = microtime(true);
        while (!$this->isEmpty()) {
            // 获取根节点
            $node = $this->top();

            if ($node['timeout'] <= time()) {
                // 取出根节点
                $this->extract();
                // 如果是重复执行,入堆
                if($node['repeat']){
                    $this->insert($node['expire'], $node['repeat'], $node['action']);
                }
                $hit = 1;

                //开启子进程
                if (pcntl_fork() == 0) {
                    empty($node['action']) ? '' : call_user_func($node['action']);
                    $pid = posix_getpid();
                    posix_kill($pid, SIGKILL);
                    // exit(0);
                }

                //忽略子进程,子进程退出由系统回收
                pcntl_signal(SIGCLD, SIG_IGN);
            }else{
                break;
            }
        }

        $t2 = microtime(true);
        echo ($debug && $hit) ? '时间堆 - 调整耗时: ' . round($t2 - $t1, 3) . "秒\r\n" : '';
    }
}


$timer = new Timer();
//注册 - 3s - 重复触发
$timer->insert(3, true, function(){
    echo '3秒 - 重复 - hello world' . "\r\n";
});

//注册 - 5s - 重复触发
$timer->insert(5, true, function(){
    echo '5秒 - 重复 - hello xxxx' . "\r\n";
});

//注册 - 3s - 触发一次
$timer->insert(3, false, function(){
    echo '3秒 - 一次 - gogo' . "\r\n";
});

//监听
$timer->monitor(true);

//注册 - 4s - 重复触发
$timer->insert(4, true, function(){
    echo '4秒 - 重复 - hello xxxx' . "\r\n";
});